package cn.tedu.mall.front.service.impl;

import cn.tedu.mall.common.exception.CoolSharkServiceException;
import cn.tedu.mall.common.restful.ResponseCode;
import cn.tedu.mall.front.mapper.FrontSkuMapper;
import cn.tedu.mall.front.service.IFrontSkuService;
import cn.tedu.mall.pojo.front.entity.FrontStockLog;
import cn.tedu.mall.pojo.order.dto.OrderItemAddDTO;
import cn.tedu.mall.pojo.product.vo.SkuStandardVO;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.config.annotation.DubboService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.TimeUnit;

@DubboService
@Service
@Slf4j
public class FrontSkuServiceImpl extends FrontCacheServiceImpl<SkuStandardVO> implements IFrontSkuService {
    @Autowired
    private FrontSkuMapper frontSkuMapper;

    public static final String FRONT_SKUS_PREFIX = "front:skus:";
    public static final String FRONT_SKUS_LOCK_SUFFIX = ".lock";

    @Override
    public List<SkuStandardVO> getSkus(Long spuId) {
        //TODO 布隆过滤器需求
        //击穿处理 分布式抢锁
        //缓存cache-aside 流程 使用List类型存储
        String skusKey = FRONT_SKUS_PREFIX + spuId;
        //没有存储和获取list缓存的方法 getListCache(skusKey)
        List<SkuStandardVO> caches = getListCache(skusKey);
        if (caches == null || caches.size() == 0) {
            //防止击穿，生成锁key值 randCode
            String skusLockKey = skusKey + FRONT_SKUS_LOCK_SUFFIX;
            String skusLockRand = new Random().nextInt(9000) + 1000 + "";
            try {
                //抢锁
                boolean haveLock = tryLock(skusLockKey, skusLockRand);
                //没抢到 等5秒 读缓存返回缓存逻辑
                if (!haveLock) {
                    Thread.sleep(5000);
                    return getListCache(skusKey);
                }
                //有没有值判断
                List<SkuStandardVO> skuStandardVOS =
                        frontSkuMapper.selectSkusBySpuId(spuId);
                if (skuStandardVOS == null) {
                    throw new CoolSharkServiceException(ResponseCode.NOT_FOUND,
                            "当前spu下没有sku数据");
                }
                //没有setListCache方法
                setListCache(skusKey, skuStandardVOS,
                        60 * 60 * 24L + new Random().nextInt(10000), TimeUnit.SECONDS);
                return skuStandardVOS;
            } catch (Exception e) {
                log.error("出现异常，e:{}", e.getMessage());
            } finally {
                releaseLock(skusLockKey, skusLockRand);
            }
        }
        return caches;
    }

    public static final String FRONT_SKU_PREFIX = "front:sku:";
    public static final String FRONT_SKU_LOCK_SUFFIX = ".lock";

    @Override
    public SkuStandardVO getSku(Long skuId) {
        //引入缓存的前提，这个方法也要考虑击穿，穿透的问题，考虑雪崩的问题
        String skuKey = FRONT_SKU_PREFIX + skuId;
        SkuStandardVO cache = getCache(skuKey);
        if (cache == null) {//缓存里没有该数据则读数据库里的
            //防止击穿
            String skuLockKey = skuKey + FRONT_SKU_LOCK_SUFFIX;
            String skuLockRand = new Random().nextInt(9000) + 1000 + "";
            try {//抢锁
                boolean haveKey = tryLock(skuLockKey, skuLockRand);
                if (!haveKey) { //没抢到 等5秒 读缓存返回缓存逻辑
                    Thread.sleep(5000);
                    return getCache(skuKey);
                }
                //读数据库数据
                SkuStandardVO skuStandardVO = frontSkuMapper.selectSkuById(skuId);
                if (skuStandardVO == null) {
                    throw new CoolSharkServiceException(
                            ResponseCode.NOT_FOUND, "当前sku不存在");
                }
                //存缓存，防止缓存雪崩问题
                setCache(skuKey, skuStandardVO,
                        60 * 60 * 24L + new Random().nextInt(10000),
                        TimeUnit.SECONDS);
                return skuStandardVO;
            } catch (CoolSharkServiceException e) {
                throw e;
            } catch (Exception e) {
                throw new CoolSharkServiceException(
                        ResponseCode.BAD_REQUEST, "不明异常");
            } finally {//释放锁
                releaseLock(skuLockKey, skuLockRand);
            }
        }
        return cache;
    }

    @Override
    public List<SkuStandardVO> listSkuByIds(List<Long> ids) {
        List<SkuStandardVO> skuStandardVOS = new ArrayList<>();
        for (Long id : ids) {
            SkuStandardVO skuStandardVO = frontSkuMapper.selectSkuById(id);
            skuStandardVOS.add(skuStandardVO);
        }
        return skuStandardVOS;
    }

    @Transactional
    @Override
    public void reduceSkusCounts(List<OrderItemAddDTO> items, String sn) {
        for (OrderItemAddDTO item : items) {
            Long skuId = item.getSkuId();
            Integer quantity = item.getQuantity();
            //update pms_sku set stock = stock-#{quantity}
            //where id=#{skuId} and stock > #{quantity}
            int success = frontSkuMapper.decrStock(skuId, quantity);
            if (success == 0) {
                throw new CoolSharkServiceException(ResponseCode.BAD_REQUEST,
                        "当前库存扣减失败,库存数量不够,skuId:" + skuId);
            }
            //成功减一个库存,记录一条库存日志
            FrontStockLog stockLog = new FrontStockLog();
            stockLog.setSkuId(skuId);
            stockLog.setOrderSn(sn);
            stockLog.setQuantity(quantity);
            stockLog.setGmtCreate(LocalDateTime.now());
            stockLog.setGmtModified(stockLog.getGmtCreate());
            frontSkuMapper.insertStockLog(stockLog);
        }
    }

    @Override
    public void returnStock(String sn) {
        //用订单sn从日志中查询减库存日志,
        List<FrontStockLog> stockLogs = frontSkuMapper.selectStockLogBySn(sn);
        if (stockLogs == null || stockLogs.size() == 0) {
            //没有这个订单的日志记录,说明库存没减过.
            log.debug("sn:{},没有对应的库存减少日志,无任何操作");
        } else {
            for (FrontStockLog stockLog : stockLogs) {
                frontSkuMapper.incrStock(stockLog.getSkuId(), stockLog.getQuantity());
            }
        }
    }
}
